package edu.gmu.atelier;

/**
 * Complex number.
 * @author James H. Pope
 */
public final class ComplexElement implements Element
    //extends Number
{
    /**
     * Multiplicative inverse requires "1" definition, c*1=c
     * (a + ib) * (x + iy) = (ax-by) + i(ay+bx)
     * (a + ib) * (1 + i0) = (a1-b0) + i(a0+b1) = (a + ib)
     */
    public static final ComplexElement ONE = new ComplexElement(1.0,0.0);
    
    /**
     * Additive inverse requires "0" definition, c+(-c)=0, c+0=0
     * (a + ib) + (x + iy) = (a+x) + i(b+y)
     * (a + ib) + (0 + i0) = (a+0) + i(b+0) = (a + ib)
     * 
     * (a + ib) + -(a + ib) = (a-a) + i(b-b) = (0 + i0)
     */
    public static final ComplexElement ZERO = new ComplexElement(0.0,0.0);
    
    // Instance variables
    public double a = 0.0;
    public double b = 0.0;
    
    public ComplexElement(double real, double imag)
    {
        this.a = real;
        this.b = imag;
    }
    
    public double getReal()
    {
        return this.a;
    }
    
    public double getImag()
    {
        return this.b;
    }
    
    //------------------------------------------------------------------------//
    // Mutable operators.
    //------------------------------------------------------------------------//
    public void multTo(Element e)
    {
        // (a + ib) * (x + iy) = (ax-by) + i(ay+bx)
        ComplexElement c2 = (ComplexElement)e;
        this.a = (this.a * c2.a) - (this.b * c2.b);
        this.b = (this.a * c2.b) + (this.b * c2.a);
    }

    public void addTo(Element e)
    {
        // (a + ib) + (x + iy) = (a+x) + i(b+y)
        ComplexElement c2 = (ComplexElement)e;
        this.a = this.a + c2.a;
        this.b = this.b + c2.b;
    }

    public void multAddTo(Element x, Element v)
    {
        ComplexElement c1 = (ComplexElement)x;
        ComplexElement c2 = (ComplexElement)v;
        double a = (c1.a * c2.a) - (c1.b * c2.b);
        double b = (c1.b * c2.a) + (c1.a * c2.b);
        
        this.a = this.a + a;
        this.b = this.b + b;
    }

    //------------------------------------------------------------------------//
    // Immutable operators, found mutable to be faster, no surprise, though
    // mitigated cons by only accessing from ComplexVector, no user interaction.
    //------------------------------------------------------------------------//
    
    public ComplexElement mult(Element e)
    {
        ComplexElement c2 = (ComplexElement)e;
        double a = (this.a * c2.a) - (this.b * c2.b);
        double b = (this.b * c2.a) + (this.a * c2.b);
        return new ComplexElement(a,b);
    }

    public ComplexElement add(Element e)
    {
        ComplexElement c2 = (ComplexElement)e;
        double a = this.a + c2.a;
        double b = this.b + c2.b;
        return new ComplexElement(a,b);
    }
    
    /**
     * Subtracts b from this element to produce a new element. 
     * @param b
     * @return this-b = this+b.negate()
     */
    public ComplexElement sub(Element e)
    {
        //return this.add(e.negate());
        ComplexElement c2 = (ComplexElement)e;
        double a = this.a - c2.a;
        double b = this.b - c2.b;
        return new ComplexElement(a,b);
    }

    public ComplexElement copy()
    {
        return new ComplexElement(this.a, this.b);
    }
    
    public ComplexElement conjugate()
    {
        return new ComplexElement(this.a, -this.b);
    }
    
    /**
     * Returns multiplicative inverse of the element.
     * @return this^(-1) such that this * this^(-1) = identity element
     */
    public ComplexElement inverse()
    {
        /*
         * http://tutorial.math.lamar.edu/Extras/ComplexPrimer/Arithmetic.aspx
         * Tricky, taken from page 171 Advanced Linear Algebra
         *     z  = complex number    = x + iy
         *     z' = complex conjugate = x - iy
         * Reciprocal = 1/z, multiply top and bottom by conjugate
         *     z' / zz' = z' / norm2
         * We want to return an complex number, note the denom is real
         *     (x / norm) + i(-y / norm2)
         * To divide w by z
         *     wz' / zz'
         */
        double norm2 = this.a * this.a + this.b * this.b;
        return new ComplexElement( this.a / norm2, -this.b / norm2 );
    }
    
    /**
     * Returns absolute value of the element (same as modulus |z| = r).
     * @return modulus
     */
    public ComplexElement abs()
    {
        // a.k.a. modulus, product complex number and conjugate
        // (x + iy)(x - iy) = x^2 + y^2
        // which is always real and positive, requirement for the norm.
        double r = Math.sqrt( this.a * this.a + this.b * this.b );
        return new ComplexElement ( r, 0.0 );
    }
    
    /**
     * Returns this number raised to the specified power.
     * @return this^exp
     */
    public ComplexElement power(double exp)
    {
        //Convert to polar form, find square root (de moivres), back to complex
        double r = Math.sqrt( this.a * this.a + this.b * this.b );
        double t = Math.acos(this.a/r);
        // z^n = r^n and t*n
        r = Math.pow(r, exp);
        t = t*exp;
        // Convert back to normal form
        double aN = r*Math.cos(t);
        double bN = r*Math.sin(t);
        return new ComplexElement ( aN, bN ); // this^n
    }
    
    /**
     * Returns square root of the element.
     * @return r such that r*r = this
     */
    public ComplexElement sqrt()
    {
        return this.power(0.5);
    }
    
    /**
     * Returns whether or not element is zero element (e+0=e, e*0=0).
     * @return true if this is zero element
     */
    public boolean isZero()
    {
        /*
         * Fairly sure this is correct -z such that z + (-z) = (0 + i0) = "0"
         */
        return (a == 0 && b == 0);
    }
    
    /**
     * Returns whether or not element (real part) is less than zero.
     * @return true if this is a negative number
     */
    public boolean isNegative()
    {
        return ( this.a < 0.0 );
    }
    
    /**
     * Returns negation of this element.
     * @return -this such that this + (-this) = zero element
     */
    public ComplexElement negate()
    {
        /*
         * The additive inverse is -z such that z + (-z) = 0
         */
        return new ComplexElement( -this.a, -this.b );
    }

    //------------------------------------------------------------------------//
    // Class instance methods
    //------------------------------------------------------------------------//
    public boolean similar( Object obj, Epsilon e )
    {
        if(obj == this)
        {
            return true;
        }
        
        if( (obj instanceof ComplexElement) == false )
        {
            return false;
        }
        
        ComplexElement o = (ComplexElement)obj;
        
        if( e != null )
        {
            if( e.similar(this.a, o.a) == false  ||
                e.similar(this.b, o.b) == false   )
            {
                return false;
            }
        }
        else
        {
            if( this.a != o.a  ||  this.b != o.b)
            {
                return false;
            }
        }
        
        return true;
    }
    
    public boolean equals( Object obj )
    {
        return this.similar(obj, null);
    }
    
    public int compareTo(Object obj)
    {
        /*
         * @TODO Need to address the imaginary part
         */
        ComplexElement o = (ComplexElement)obj;
        if(      this.a > o.a ) return +1;
        else if( this.a < o.a ) return -1;
        return 0;
    }
    
    public String toString()
    {
        StringBuilder buf = new StringBuilder("(");
        buf.append(this.a);
        buf.append(",");
        buf.append(this.b);
        buf.append(")");
        return buf.toString();
    }

//    @Override
//    public int intValue() {
//        throw new UnsupportedOperationException("Not supported yet.");
//    }
//
//    @Override
//    public long longValue() {
//        throw new UnsupportedOperationException("Not supported yet.");
//    }
//
//    @Override
//    public float floatValue() {
//        throw new UnsupportedOperationException("Not supported yet.");
//    }

    public double doubleValue()
    {
        return this.a;
    }

    //------------------------------------------------------------------------//
    // Static accessor methods, does not affect instance of class
    //------------------------------------------------------------------------//
    public Element getZero()
    {
        return ZERO;
    }
    
    public Element getOne()
    {
        return ONE;
    }

}
